#include "DateTime.h"

bool NahidaProject::DateTime::IsLeapYear(int year) const {
	return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

int NahidaProject::DateTime::GetDaysInMonth(int year, int month) const {
	static const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if (month == 2 && IsLeapYear(year)) {
		return 29;
	}
	return days[month - 1];
}

void NahidaProject::DateTime::Validate() const {
	if (year < 1 || year > 9999) {
		throw std::invalid_argument("Invalid year");
	}
	if (month < 1 || month > 12) {
		throw std::invalid_argument("Invalid month");
	}
	if (day < 1 || day > GetDaysInMonth(year, month)) {
		throw std::invalid_argument("Invalid day");
	}
	if (hour < 0 || hour > 23) {
		throw std::invalid_argument("Invalid hour");
	}
	if (minute < 0 || minute > 59) {
		throw std::invalid_argument("Invalid minute");
	}
	if (second < 0 || second > 59) {
		throw std::invalid_argument("Invalid second");
	}
}

long long NahidaProject::DateTime::ToJulianDay() const {
	int a = (14 - month) / 12;
	int y = year + 4800 - a;
	int m = month + 12 * a - 3;
	return day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
}

void NahidaProject::DateTime::FromJulianDay(long long jd) {
	long long a = jd + 32044;
	long long b = (4 * a + 3) / 146097;
	long long c = a - (146097 * b) / 4;
	long long d = (4 * c + 3) / 1461;
	long long e = c - (1461 * d) / 4;
	long long m = (5 * e + 2) / 153;

	day = e - (153 * m + 2) / 5 + 1;
	month = m + 3 - 12 * (m / 10);
	year = 100 * b + d - 4800 + m / 10;
}

NahidaProject::DateTime& NahidaProject::DateTime::operator=(const DateTime& other) {
	if (this != &other) {
		year = other.year;
		month = other.month;
		day = other.day;
		hour = other.hour;
		minute = other.minute;
		second = other.second;
	}
	return *this;
}

NahidaProject::DateTime NahidaProject::DateTime::Now() {
	std::time_t t = std::time(nullptr);
	std::tm* localTime = std::localtime(&t);
	return DateTime(
		localTime->tm_year + 1900,
		localTime->tm_mon + 1,
		localTime->tm_mday,
		localTime->tm_hour,
		localTime->tm_min,
		localTime->tm_sec
	);
}

NahidaProject::DateTime NahidaProject::DateTime::Parse(const std::string& str, const std::string& format) {
	std::tm time = {};
	std::istringstream ss(str);
	ss >> std::get_time(&time, format.c_str());
	if (ss.fail()) {
		throw std::invalid_argument("Failed to parse date time string");
	}
	return DateTime(
		time.tm_year + 1900,
		time.tm_mon + 1,
		time.tm_mday,
		time.tm_hour,
		time.tm_min,
		time.tm_sec
	);
}

int NahidaProject::DateTime::GetYear() const { return year; }
int NahidaProject::DateTime::GetMonth() const { return month; }
int NahidaProject::DateTime::GetDay() const { return day; }
int NahidaProject::DateTime::GetHour() const { return hour; }
int NahidaProject::DateTime::GetMinute() const { return minute; }
int NahidaProject::DateTime::GetSecond() const { return second; }

void NahidaProject::DateTime::SetYear(int y) {
	DateTime temp(y, month, day, hour, minute, second);
	*this = temp;
}

void NahidaProject::DateTime::SetMonth(int m) {
	DateTime temp(year, m, day, hour, minute, second);
	*this = temp;
}

void NahidaProject::DateTime::SetDay(int d) {
	DateTime temp(year, month, d, hour, minute, second);
	*this = temp;
}

void NahidaProject::DateTime::SetHour(int h) {
	DateTime temp(year, month, day, h, minute, second);
	*this = temp;
}

void NahidaProject::DateTime::SetMinute(int min) {
	DateTime temp(year, month, day, hour, min, second);
	*this = temp;
}

void NahidaProject::DateTime::SetSecond(int s) {
	DateTime temp(year, month, day, hour, minute, s);
	*this = temp;
}

std::string NahidaProject::DateTime::ToString(const std::string& format) const {
	std::tm time = {};
	time.tm_year = year - 1900;
	time.tm_mon = month - 1;
	time.tm_mday = day;
	time.tm_hour = hour;
	time.tm_min = minute;
	time.tm_sec = second;

	std::ostringstream ss;
	ss << std::put_time(&time, format.c_str());
	return ss.str();
}

double NahidaProject::DateTime::operator-(const DateTime& other) const {
	long long differentDays = ToJulianDay() - other.ToJulianDay();
	long long differentSeconds = differentDays * 24 * 3600;
	differentSeconds += (hour - other.hour) * 3600;
	differentSeconds += (minute - other.minute) * 60;
	differentSeconds += (second - other.second);
	return static_cast<double>(differentSeconds);
}

NahidaProject::DateTime NahidaProject::DateTime::operator+(int seconds) const {
	DateTime result(*this);
	result.AddSeconds(seconds);
	return result;
}

NahidaProject::DateTime NahidaProject::DateTime::operator-(int seconds) const {
	DateTime result(*this);
	result.AddSeconds(-seconds);
	return result;
}

NahidaProject::DateTime& NahidaProject::DateTime::operator+=(int seconds) {
	AddSeconds(seconds);
	return *this;
}

NahidaProject::DateTime& NahidaProject::DateTime::operator-=(int seconds) {
	AddSeconds(-seconds);
	return *this;
}

void NahidaProject::DateTime::AddSeconds(int seconds) {
	long long totalSeconds = static_cast<long long>(ToJulianDay()) * 24 * 3600 + hour * 3600 + minute * 60 + second + seconds;
	long long days = totalSeconds / (24 * 3600);
	long long remainingSeconds = totalSeconds % (24 * 3600);

	if (remainingSeconds < 0) {
		days--;
		remainingSeconds += 24 * 3600;
	}

	FromJulianDay(days);

	hour = remainingSeconds / 3600;
	remainingSeconds %= 3600;
	minute = remainingSeconds / 60;
	second = remainingSeconds % 60;

	Validate();
}

bool NahidaProject::DateTime::operator==(const DateTime& other) const {
	return year == other.year && month == other.month && day == other.day && hour == other.hour && minute == other.minute && second == other.second;
}

bool NahidaProject::DateTime::operator!=(const DateTime& other) const {
	return !(*this == other);
}

bool NahidaProject::DateTime::operator<(const DateTime& other) const {
	if (year != other.year) return year < other.year;
	if (month != other.month) return month < other.month;
	if (day != other.day) return day < other.day;
	if (hour != other.hour) return hour < other.hour;
	if (minute != other.minute) return minute < other.minute;
	return second < other.second;
}

bool NahidaProject::DateTime::operator<=(const DateTime& other) const {
	return *this < other || *this == other;
}

bool NahidaProject::DateTime::operator>(const DateTime& other) const {
	return !(*this <= other);
}

bool NahidaProject::DateTime::operator>=(const DateTime& other) const {
	return !(*this < other);
}

int NahidaProject::DateTime::GetWeekday() const {
	long long jd = ToJulianDay();
	return (jd + 1) % 7;
}

int NahidaProject::DateTime::GetDayOfYear() const {
	int days = 0;
	for (int i = 1; i < month; i++) {
		days += GetDaysInMonth(year, i);
	}
	days += day;
	return days;
}

bool NahidaProject::DateTime::IsLeapYear() const {
	return IsLeapYear(year);
}
